package com.lagou.rpc.server;

import com.lagou.rpc.common.entity.RPCMethodMetaData;
import com.lagou.rpc.common.entity.RPCServiceMetaData;
import com.lagou.rpc.common.exceptions.SimpleRpcException;
import com.lagou.rpc.common.utils.MethodIdUtil;
import com.lagou.rpc.common.utils.ReflectionUtil;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 简单rpc方法注册中心
 *
 * @author wlz
 * @date 2020/6/16
 */
public class SimpleRpcMethodRegistry {

    private Map<String, SimpleRpcMethod> rpcMethodMap;

    private Map<String, RPCServiceMetaData> serviceMetaDataMap;


    public SimpleRpcMethodRegistry() {
        rpcMethodMap = new ConcurrentHashMap<>();
        serviceMetaDataMap = new ConcurrentHashMap<>();
    }

    /**
     * 获取服务方法数据
     *
     * @param methodId 方法id
     * @return 返回方法
     */
    public SimpleRpcMethod getRpcMethod(String methodId) {
        return rpcMethodMap.get(methodId);
    }

    /**
     * 注册方法
     *
     * @param methodId        方法id
     * @param simpleRpcMethod 服务方法数据
     */
    private synchronized void registerMethod(String methodId, SimpleRpcMethod simpleRpcMethod) {
        if (rpcMethodMap.containsKey(methodId)) {
            throw new SimpleRpcException(MessageFormat.format("methodId {0} already exist", methodId));
        }
        rpcMethodMap.put(methodId, simpleRpcMethod);
    }

    /**
     * 注册整个服务
     *
     * @param serviceInterface 服务接口
     * @param serviceInstance  服务实例
     */
    public synchronized void registerService(Class serviceInterface, Object serviceInstance) {
        if (serviceInstance == null) {
            throw new SimpleRpcException("serviceInstance cannot be null");
        }

        // 构建服务元数据
        RPCServiceMetaData serviceMetaData = buildServiceMetaData(serviceInterface, serviceInstance);

        // 注册服务实例方法
        serviceMetaDataMap.put(serviceMetaData.getServiceName(), serviceMetaData);
    }

    public RPCServiceMetaData getServiceMetaData(String serviceName) {
        return serviceMetaDataMap.get(serviceName);
    }


    private RPCServiceMetaData buildServiceMetaData(Class serviceType, Object instance) {
        String serviceClassName = serviceType.getName();
        // 判断是否有冲突
        if (serviceMetaDataMap.containsKey(serviceClassName)) {
            throw new SimpleRpcException(MessageFormat.format("service {0} already exist", serviceClassName));
        }
        RPCServiceMetaData rpcServiceMetaData = new RPCServiceMetaData();
        rpcServiceMetaData.setServiceName(serviceClassName);
        // 解析所有的方法,提取方法的元数据信息
        List<Method> allMethods = ReflectionUtil.getAllMethods(serviceType);
        if (allMethods == null || allMethods.isEmpty()) {
            return rpcServiceMetaData;
        }
        Map<String, RPCMethodMetaData> methodMetaDataMap = allMethods.stream()
                .map(method -> buildRpcMethodMataData(serviceClassName, instance, method))
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(RPCMethodMetaData::getMethodId, m -> m));
        rpcServiceMetaData.setMethodMetaDataMap(methodMetaDataMap);
        return rpcServiceMetaData;
    }


    private RPCMethodMetaData buildRpcMethodMataData(String serviceClassName, Object instance, Method method) {
        // 获取方法参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        String[] paramTypes = getClassNames(parameterTypes);
        // 使用md5根据方法特征生成methodId
        String methodId = MethodIdUtil.getMethodId(serviceClassName, method.getName(), paramTypes);
        // 构建方法元数据信息
        RPCMethodMetaData rpcMethodMetaData = new RPCMethodMetaData()
                .setClassName(serviceClassName)
                .setMethodId(methodId)
                .setMethodName(method.getName())
                .setParamTypes(parameterTypes)
                .setParamTypeNames(paramTypes);
        // 注册服务方法
        SimpleRpcMethod simpleRpcMethod = new SimpleRpcMethod()
                .setMethodId(methodId)
                .setMethod(method)
                .setMethodMetaData(rpcMethodMetaData)
                .setServiceInstance(instance);
        registerMethod(methodId, simpleRpcMethod);
        return rpcMethodMetaData;
    }

    private String[] getClassNames(Class<?>[] parameterTypes) {
        if (parameterTypes == null || parameterTypes.length == 0) {
            return null;
        }
        String[] paramTypes = new String[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            paramTypes[i] = parameterTypes[i].getTypeName();
        }
        return paramTypes;
    }

}
